Friday, August 24, 2018

Removing a Route parameter from an ActionLink in MVC4

I am having a bit of a problem with a RouteData Value lingering in my View and making my @Html.ActionLink(...) look a bit ugly.

In my routes I have this:

routes.MapRoute(
    name: "SubItemLinked",
    url: "SubItems/{subitemId}/{controller}/{action}/{id}",
    defaults: new { controller = "DeepDownItems", action = "Index", id = UrlParameter.Optional },
    constraints: new { subitemId = "^[0-9]*$", id = "^[0-9]*$" }
);

routes.MapRoute(
    name: "ItemLinked",
    url: "Items/{itemId}/{controller}/{action}/{id}",
    defaults: new { controller = "SubItems", action = "Index", id = UrlParameter.Optional },
    constraints: new { itemId = "^[0-9]*$", id = "^[0-9]*$" }
);

The above allows me to have Urls like these:

  • ~/Items/3 <= will show all SubItems for Item number 3
  • ~/Items/3/SubItems/Create <= exactly what you would expect
  • ~/SubItems/5 <= to show all DeepDownItems of SubItem number 5

and so on...

Now, of course the DeepDownItems controller will recognise the subitemId parameter and use it for retrieval of stuff from the DB. Problem is that if I want to put a link back to the SubItems list, I don't need this subitemId anymore and I need to remove it from the RouteValueDictionary.

As an example of the flow, let's assume I go to ~/SubItems/5, I see the list of all DeepDownItems of SubItem 5, which is correct. In that View I have this:

@Html.ActionLink("Go Back", "Index", "SubItems", new { itemId = ViewBag.itemid, subitemId = String.Empty }, null)

This should generate, according to the Routes, a link to ~/Items/3. I had to put subitemId = String.Empty or it would have matched with the top route. Problem is that the url being generated is like this ~/Items/3?subitemId=5, so basically is matching the Route correctly but then it is not clearing the subitemId parameter as instructed.

Do you have any ideas on how to do it? Notice that I have other links (Edit/Create) on the View that rely on that parameter being present and added automatically so I would really like to only remove it from that link rather than the whole Dictionary.

Solved

Not sure if it's the same issue I've been fighting for the last few hours, but if it is: Try using @Html.RouteLink where you can specify which route to create a link from.

Something like the following, if I've managed to decipher your example :P

@Html.RouteLink("Go Back", "ItemLinked", new {
        controller = "SubItems",
        action = "Index", 
        itemId = ViewBag.itemid,
    })

Monday, August 20, 2018

Javascript code: how does it work?

I recently completed the DevTools ~> http://discover-devtools.codeschool.com/ course and while inspecting the code used I came across some sections which I did not understand:

String.prototype.repeat = function (num) {
  return new Array(num + 1).join(this);
};

used in the displayDate() method thus:

var body = $('body')[0].outerHTML.repeat(10000);

What is the importance of using this code?

Secondly, within displayToday() method, displayDate() method is called with an argument although it is not defined to take an arg. Why is this so?

I am learning JS and couldn't npt wrap my head around these. Any help is welcome.

Solved

String.prototype.repeat = function (num) {
  return new Array(num + 1).join(this);
};

This code creates an array that is num+1 in length, filled with undefined. It is then collapsed into a string using join, separating each undefined value with the context this which is the outerHTML string. When an array is joined into a string, undefined values are just nothing, thus the string generated only contains your separators which appear num times, thereby "repeating" the string.

//let . be "undefined, which when turned into a string, is just nothing
[u,u,u,...,u].join('test');
'(u)test(u)test(u)...(u)test'
'testtest...test'

As for the second question, functions in JS will always take in arguments, even if the function isn't meant to take one. Defining arguments in the function in JS is simply assigning the passed arguments a name. The arguments will always go to the function, and collected in a special array available in functions as the arguments variable.

function fn(){
  console.log(arguments); //['hello','world'];
}

foo('hello','world');

You can pass an argument to a function even if you don't have it listed in the declaration. You can access arguments passed with arguments

function foo () {
  console.log(arguments);
}

foo('bar'); // ['bar']
foo('bar','blerg'); // ['bar','blerg']

It's very simple.

When you alter the prototype of something, in this case the default String class, you make that method available to any instance of that class, in this case to any string.

Now let's look at what the method does.

return new Array(num + 1).join(this);

Because of String.prototype.displayDate = function(num) {, the value of this inside that function is the value of the string. The this reference points to the current object, I guess that makes sense.

Then it's creating an array of num + 1 elements, which will all be initialised with undefined. Array.prototype.join returns the string representation of the array elements separated by the thing you provide as an argument to it.

in this case, you have an array of num +1 undefined values. the string representation of undefined is "", or the empty string. So you end up having num + 1 concatenations of the empty string + the value of this, or num times your string.

Say your string is "test", and you call repeat(2).

It first created a = new Array(undefined, undefined, undefined);// 2 + 1 times.

Then it starts to join the strings, putting "test" in between each pair.

new String(undefined) + new String("test") + new String(undefined); + new String("test") + new String(undefined)

The above becomes:

"" + "test" + "" + "test" + "" = "testtest"// two times the original string.

Sunday, August 19, 2018

Existing project run with Laravel homestead (5.4)

I have installed Laravel homestead it's working fine. my problem is how I map existing project to homestead? my Homestead.yaml file as bellow

---
ip: "192.168.10.10"
memory: 2048
cpus: 1
provider: virtualbox

authorize: ~/.ssh/id_rsa.pub

keys:
    - ~/.ssh/id_rsa

folders:

    - map: D:/www/Laravel
      to: /home/vagrant/Code/Laravel  


sites:
    - map: homestead.app
      to: /home/vagrant/Code/Laravel/public



databases:
    - homestead 

This project generated from Homestead I have another exist project and how to map homestead.I added following code to Homestead.yaml file but it was not working.

 - map: D:/www/MyProject
      to: /home/vagrant/Code/MyProject 

Please anyone can help me Thank you.

Solved

you should edit your folders section to map to you www directory

folders:

    - map: D:/www
      to: /home/vagrant/Code

this will allow you to store all of your projects within D:/www directory and all of them will be mapped to your VM.

now you can add your Site

you should add this under Sites

 - map: my-project.app
   to: /home/vagrant/Code/MyProject/public

this assumes that your project is located at D:/www/MyProject


also don't forget to add new domain to your hosts file

192.168.10.10    my-project.app

and run vagrant reload --provision


Try this:

---
ip: "192.168.10.10"
memory: 2048
cpus: 1
provider: virtualbox

authorize: ~/.ssh/id_rsa.pub

keys:
    - ~/.ssh/id_rsa

folders:

    - map: D:/www/
      to: /home/vagrant/Code/


sites:
    - map: MyProject.app
      to: /home/vagrant/Code/MyProject/public


databases:
    - homestead 

Make sure that:

1) There is a code folder inside C:/users/{currentuser}/

2) You have MyProject folder in D:/www

3) A virtual hosts file entry: Vagrant box IP MyProject.app

And at last fire vagrant reload --provision command within C:/users/{currentuser}/Homestead folder


Friday, August 17, 2018

Is there any way to install a package in all python enviroments

I have created several conda environments of python. But sometimes, I encounter some utility package that can be helpful to all the environments that I have on my system. Is there any way to do this without switching back and forth between all the environments and installing them individually.

Thanks

Solved

In that case you can install the package in the base (root) environment. All packages related with command line utilities (example git) and graphical user interfaces (example spyder) in that default environment are visibles in all your conda environments.

Update

You can use my script. Uncomment last 3 lines and change list of packages. You can use a manual list of envs or use automatic in all envs.

import subprocess as sub

def conda_env_list():
    p = sub.Popen("conda env list", shell=True, stdout=sub.PIPE, encoding="utf-8")
    p.wait()
    out = p.communicate()[0].splitlines()
    envs = [out[line].split()[0] for line in range(2, len(out)-1)]
    return envs

def conda_env_install(envs, packages, channel="default"):
    TEMPLATE = "conda install {confirm} -c {channel} -n {env} {packages} "
    if isinstance(envs, str):
        envs = [envs]
    if isinstance(packages, list):
        packages = " ".join(packages)
    confirm = "-y"
    for env in envs:
        cmd = TEMPLATE.format(confirm=confirm, packages=packages, \
            channel=channel, env=env)
        p = sub.Popen(cmd, shell=True, stdout=sub.PIPE, encoding="utf-8")
        p.wait()
        print(p.communicate()[0])

envs = conda_env_list()
packages = ["git"]
conda_env_install(envs, packages)

If you store all you virtual environments in one place or if you already use virtualenvwrapper that stores virtualenv in ~/.virtualenvs/ you can use command allvirtualenv from virtualenvwrapper; if you don't use virtualenvwrapper you have to install it first.

allvirtualenv pip install somepackages

I use the following bash script to run a command over all environments in ~/.virtualenvs/ and in~/.tox/:

#! /usr/bin/env bash

if source virtualenvwrapper.sh; then
   allvirtualenv eval "$@"

   for tox_envs in ~/.tox/*; do
      if [[ "$tox_envs" = */.tox/\* ]]; then
         exit 0
      fi
      WORKON_HOME="$tox_envs" allvirtualenv eval "$@"
   done
fi