顯示具有 JavaScript 標籤的文章。 顯示所有文章
顯示具有 JavaScript 標籤的文章。 顯示所有文章

2017年2月18日 星期六

Redux的createStore實作

建立一個計數器範例,點擊頁面會執行累加計數
直接使用 createStore:
import { createStore } from 'redux';

const counter = (state = 0, action) => {
    switch (action.type) {
        case 'INCREMENT':
            return state + 1;
        case 'DECREMENT':
            return state - 1;
        default:
            return state;
    }
}

const store = createStore(counter);
console.log(`initial state : ${store.getState()}`);

store.subscribe(() => {
    document.body.innerHTML = store.getState();
})

document.addEventListener('click', () => {
    store.dispatch({ type: 'INCREMENT' });

});

現在 counter 方法直接沿用,另外建立一個 createStore 方法:
const createStore = (reducer) => {
    // 這個 store 持有 state 變數
    let state;

    const getState = () => state;

    const dispatch = (action) => {

    };

    const subscribe = (listener) => {

    };

    // 回傳的物件被稱為 Redux store
    return { getState, dispatch, subscribe }

};

因為 subscribe() 可以被呼叫很多次,所以需要紀錄這些 listener:
    let listeners = [];

    const subscribe = (listener) => {
        listeners.push(listener)
    };

dispatch() 是唯一可以改變內部 state 的:
    const dispatch = (action) => {
        // 使用當前的 state 和 被 dispatch 的 action 物件當參數呼叫 reducer 計算出新的 state
        state = reducer(state, action);
        // 執行 listeners
        listeners.forEach((listener) => { listener() });
    };

還沒有實作 unsubscribe() 方法,先使用替代方案,在subscribe() 寫一個回傳一個方法:
        // 回傳一個方法
        // 使用方式:
        //     var s1 = store.subscribe(()=>{});
        //     s1(); // unsubscribe
        return () => {
            listeners = listeners.filter(l => l !== listener);
        }

最後在 createStore() 回傳前加入 dispatch({});
為了得到初始 state

createStore 完整範例:
const createStore = (reducer) => {
    let state;
    let listeners = [];

    const getState = () => state;

    const dispatch = (action) => {
        state = reducer(state, action);
        listeners.forEach((listener) => { listener() });
    };

    const subscribe = (listener) => {
        listeners.push(listener);
        return () => {
            listeners = listeners.filter(l => l !== listener);
        }
    };

    dispatch({});

    return { getState, dispatch, subscribe }

};










2016年8月11日 星期四

drag and drop upload files with asp.net core





前端 HTML : 
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <style>
        body {
            font-family"Arial",sans-serif;
        }
 
        .dropzone {
            width300px;
            height300px;
            border2px dashed #ccc;
            color#ccc;
            line-height300px;
            text-aligncenter;
        }
 
            .dropzone.dragover {
                border-color#000;
                color#000;
            }
    </style>
</head>
<body>
    <div id="uploads"></div>
    <div class="dropzone" id="dropzone">Drop files here to append file</div>
    <ul id="files-to-upload"></ul>
    <input type="text" name="tags" id="tags" value="blue,whatever" />
    <input type="text" name="name" id="name" value="name" />
    <button onclick="onUpload()">upload</button>
    <script>
        var dropzone = document.getElementById('dropzone'),
            filesToUpload = document.getElementById('files-to-upload'),
            fileArr = [];
 
        var appendFiles = function (files) {
            console.log(files);
            for (var i = 0; i < files.length; i++) {
                filesToUpload.insertAdjacentHTML('beforeend', '<li>' + files[i].name + '</li>');
                fileArr.push(files[i]);
            }
        };
 
        var onUpload = function () {
            var xhr = new XMLHttpRequest(), formData = new FormData();
            for (var i = 0; i < fileArr.length; i++) {
                formData.append('files', fileArr[i]);
            }
            // append metadata
            formData.append('tags', document.getElementById('tags').value);
            formData.append('name', document.getElementById('name').value);
            console.log(formData);
            //
            xhr.onload = function () { // success
                var data = this.responseText;
                while (filesToUpload.firstChild) { //  faster than "filesToUpload.innerHTML = '';"
                    filesToUpload.removeChild(filesToUpload.firstChild);
                }
                fileArr = []; // empty file list
                console.log(data);
            };
            xhr.open('post', '/Home/Upload');
            xhr.send(formData);
 
        }
 
        dropzone.ondrop = function (e) {
            e.preventDefault(); // 避免瀏覽器開啟圖片
            this.className = 'dropzone';
 
            appendFiles(e.dataTransfer.files);
        };
 
        dropzone.ondragover = function () {
            this.className = 'dropzone dragover';
            return false;
        };
 
        dropzone.ondragleave = function () {
            this.className = 'dropzone';
            return false;
        };
    </script>
</body>
</html>




=========================
後端 ASP.NET Core 

[HttpPost]
public async Task<IActionResult> Upload(ICollection<IFormFile> files, string tags, string name)
{
    foreach (var file in files)
    {
        if (file.Length > 0)
        {
            using (var fileStream = new FileStream(Path.Combine("wwwroot/images", file.FileName), FileMode.Create))
            {
                await file.CopyToAsync(fileStream);
            }
        }
    }
    return Content("A12345678");
}

2015年8月14日 星期五

angular directive 連動選單

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>angular directive 連動選單</title>
    <script src="../Scripts/angular.min.js"></script>
    <script>
        var app = angular.module('myApp', []);
 
        // directive
        app.directive('categorySelection', function ($http) {
            var _template = '<div>' +
                             // 第一分類
                                '<select class="category-first form-control" ng-model="selectedCategory.first">' +
                                    '<option value="0">--第一分類--</option>' +
                                    '<option ng-repeat="f in categorySelection.firsts" value="{{f.Id}}">{{f.Text}}</option>' +
                                '</select>' +
                                // 第二分類
                                '<select class="category-second form-control" ng-model="selectedCategory.second">' +
                                    '<option value="0">--第二分類--</option>' +
                                     '<option ng-repeat="s in categorySelection.seconds" value="{{s.Id}}">{{s.Text}}</option>' +
                                '</select>' +
                                // 第三分類
                                '<select class="category-third form-control" ng-model="selectedCategory.third">' +
                                    '<option value="0">--第三分類--</option>' +
                                    '<option ng-repeat="t in categorySelection.thirds" value="{{t.Id}}">{{t.Text}}</option>' +
                                '</select>' +
                            '</div>';
 
            return {
                restrict: 'E',
                scope: {
                    first: '=', // 第一分類, 用"等於"符號做雙向綁定
                    second: '=', // 第二分類
                    third: '=' // 第三分類
                },
                controller: function ($scope) {
                    // 已經被選取的分類
                    $scope.selectedCategory = {
                        first: 0,
                        second: 0,
                        third: 0
                    };
                    // 分類選項
                    $scope.categorySelection = {};
                    $scope.categorySelection.firsts = [];
                    $scope.categorySelection.seconds = [];
                    $scope.categorySelection.thirds = [];
                },
                replace: true,
                template: _template,
                link: function (scope) {
                    // 取第一類選項資料
                    refeshCategorySelection('first');
 
                    // 第一分類選項異動, 更新第二分類選項
                    scope.$watch("selectedCategory.first", function () {
                        scope.first = parseInt(scope.selectedCategory.first, 10);
 
                        if (scope.first !== 0) {
                            refeshCategorySelection('second', scope.first);
                        } else {
                            scope.categorySelection.seconds = [];
                            scope.categorySelection.thirds = [];
                        }
                    });
                    // 第二分類選項異動, 更新第三分類選項
                    scope.$watch("selectedCategory.second", function () {
                        scope.second = parseInt(scope.selectedCategory.second, 10);
 
                        if (scope.second !== 0) {
                            refeshCategorySelection('third', scope.second);
                        } else {
                            scope.categorySelection.thirds = [];
                        }
                    });
                    // 第三分類選項異動
                    scope.$watch("selectedCategory.third", function () {
                        scope.third = parseInt(scope.selectedCategory.third, 10);
                    });
                    // 更新選單
                    function refeshCategorySelection(type, id) {
                        $http.get('/Home/GetData?id=' + id).success(function (data) {
                            switch (type) {
                                case 'first':
                                    scope.categorySelection.firsts = data;
                                    scope.categorySelection.seconds = [];  // 清空第二分類選單 (template上剩下預設的"--第二分類--"選項)
                                    scope.categorySelection.thirds = [];
                                    scope.selectedCategory.first = 0; // 設定館分類被選取值 (讓被選取值回到"--第一分類--"選項上)
                                    scope.selectedCategory.second = 0;
                                    scope.selectedCategory.third = 0;
                                    break;
                                case 'second':
                                    scope.categorySelection.seconds = data;
                                    scope.categorySelection.thirds = [];
                                    scope.selectedCategory.second = 0;
                                    scope.selectedCategory.third = 0;
                                    break;
                                case 'third':
                                    scope.categorySelection.thirds = data;
                                    scope.selectedCategory.third = 0;
                                    break;
                                default:
                                    break;
                            }
                        });
 
                    }
                }
            }
        });
 
        // controller
        app.controller('homeCtrl', function ($scope) {
            $scope.query = {};
            $scope.query.first = 0;
            $scope.query.second = 0;
            $scope.query.third = 0;
 
            $scope.onSend = function () {
                console.log($scope.query);
            };
        });
    </script>
</head>
<body ng-app="myApp" ng-controller="homeCtrl">
    <category-selection first="query.first" second="query.second" third="query.third"></category-selection><button ng-click="onSend()">Send</button>
</body>
</html>

2015年1月19日 星期一

AJAX 與 [Authorize]


ASP.NET MVC 在 Controller 或 Action 上加上 [Authorize] 就可以驗證是否已經登入,如果沒有登入就會被帶往登入頁面

當使用ajax方式則會得到狀態為200的頁面(指定的頁面),因此需要改寫可以回傳一個Json :

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            filterContext.Result = new JsonResult
            {
                Data = new 
                { 
                    // put whatever data you want which will be sent
                    // to the client
                    message = "sorry, but you were logged out" 
                },
                JsonRequestBehavior = JsonRequestBehavior.AllowGet
            };
        }
        else
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
    }
}

$.get('@Url.Action("SomeAction")', function (result) {
    if (result.message) {
        alert(result.message);
    } else {
        // do whatever you were doing before with the results
    }
});
如果是用 AngularJs,IsAjaxRequest 會一直判斷是 false,因為 AngularJs 的 ajax 呼叫沒有包含 X-Requested-With 表頭,而 ASP.NET MVC 是用這個表來判斷是否為一個 ajax 呼叫,所以必須改為:

var productsApp = angular.module('productsApp', []);
productsApp.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest'
}]);





2014年10月15日 星期三

JQuery 的 on() 原理


在JavaScript中,大多數事件會在DOM裡往上一層一層的泡浮,也就是說,當一個元素觸發一個事件時,她會在DOM一直往上泡浮到 document 層級

舉例,現在有個基本的html:
<div>
   <span>1</span>
   <span>2</span>
</div>
現在,我們採用事件委託:
$('div').on('click', 'span', fn);
該事件處理程序僅僅連接到 div 元素,由於 span 是在 div 裡面,對 span click 會往上泡浮到 div,並觸發 div 的 click 事件處理器,同時,剩下要做的就是檢查 event.target 是否符合我們指定的目標(範例中為 span)

看看更複雜點的範例:
<div id="parent">
    <span>1</span>
    <span>2 <b>another nested element inside span</b></span>
    <p>paragraph won't fire delegated handler</p>
</div>
下面是一些基礎邏輯:

// 對祖先附加處理程序
document.getElementById('parent').addEventListener('click', function(e) {
    // 過濾 event target
    if (e.target.tagName.toLowerCase() === 'span') {
        console.log('span inside parent clicked');
    }
});
上面的程式中,當 event.target 是被嵌套在過濾器裡面時就無法匹配(例如第二個span裡面有b,click她時,會被上面的程式過濾掉),所以我們需要一些迭代邏輯
document.getElementById('parent').addEventListener('click', function(e) {
    var failsFilter = true,
        el = e.target;
    while (el !== this && (failsFilter = el.tagName.toLowerCase() !== 'span') && (el = el.parentNode));
    if (!failsFilter) {
        console.log('span inside parent clicked');
    }
});